package jtg.generator.path;

import soot.Unit;
import soot.toolkits.graph.UnitGraph;

import java.util.*;

public class StatementCoverage {
    private Set<List<Unit>> statementCoveragePaths;

    public Set<List<Unit>> getStatementCoveragePaths() {
        return statementCoveragePaths;
    }

    public StatementCoverage(UnitGraph ug){
        this.statementCoveragePaths=calStatementCoverage(ug);

    }
    public static Set<List<Unit>> calStatementCoverage(UnitGraph ug) {
        Map<String,Integer > map=new HashMap<String, Integer>();
        //构造一个map存放节点和基本快的对应关系
        int index=0;
        for(Unit unit:ug){
            String uid=getID(unit);
            index+=1;
            map.put(uid,index);
        }

        Set<List<Unit>> paths = new HashSet<>();
        Set<Unit> visited=new HashSet<>();
        int pathNum=0;
        List<Unit> preceeds=new ArrayList<>();
        List<Unit> succeeds=new ArrayList<>();
        // 语句覆盖算法：每个点往前后找节点，形成一条完整路径，同时用visited记录已经覆盖到的，覆盖到的就不用再扩展了，没覆盖的才扩展
        for(Unit current:ug){
            if(!visited.contains(current)){
                List<Unit> currentPath = new ArrayList<>();
                System.out.println("这是当前节点："+current);
                currentPath.add(current);
                pathNum+=1;
                //找到并添加前继
                if(!ug.getHeads().contains(current)){
                    preceeds=extendPreds(ug,currentPath);
                    List<Integer> preceedsWithIndex=new ArrayList<Integer>();
                    //对应到数字节点
                    assert preceeds != null;
                    for(Unit unit:preceeds){
                        String uid=getID(unit);
                        int idx=map.get(uid);
                        preceedsWithIndex.add(idx);
                    }
                    System.out.println("这是节点"+map.get(getID(current))+"的前继："+preceedsWithIndex);
                    //拼接所有前继
                    preceeds.remove(0);//去掉第一个节点因为本来的path里面就有
                    Collections.reverse(preceeds);//逆序
                    currentPath.addAll(0,preceeds);//加到最前面

                }
                //找到并添加后继
                if(!ug.getTails().contains(current)){//判断最后一个是不是尾节点
                    succeeds=extendSuccs(ug,currentPath);
                    List<Integer> succeedsWithIndex=new ArrayList<Integer>();
                    //对应到数字节点
                    assert succeeds != null;
                    for(Unit unit:succeeds){
                        String uid=getID(unit);
                        int idx=map.get(uid);
                        succeedsWithIndex.add(idx);
                    }
                    System.out.println("这是节点"+map.get(getID(current))+"的后继："+succeedsWithIndex);
                    //拼接后继
                    succeeds.remove(0);
                    currentPath.addAll(succeeds);
                }
                paths.add(currentPath);//处理完的完整路径加到allCompletePaths中
                visited.addAll(currentPath);
            }

        }

        //去掉冗余
        Set<List<Unit>> redundant=new HashSet<>();
        for(List<Unit> path1:paths){
            for(List<Unit> path2:paths){
                if(!path1.equals(path2)&&new HashSet<>(path1).containsAll(path2)){
                    redundant.add(path2);
                }
            }
        }
        paths.removeAll(redundant);

        pathNum=0;
        // 打印所有路径
        System.out.println("以下路径满足语句覆盖：");
        for (List<Unit> path : paths) {
            pathNum+=1;
            List<Integer> pathWithIndex=new ArrayList<Integer>();
            for(Unit unit:path){
                String uid=getID(unit);
                int idx=map.get(uid);
                pathWithIndex.add(idx);
            }
            System.out.println("Path: " + path);
            System.out.println("Path "+pathNum+" in Index: "+pathWithIndex);
        }
        return paths;
    }

    /**
     * 拓展后继节点到tails出口
     * @param ug
     * @param path
     * @return
     */
    private static List<Unit> extendSuccs(UnitGraph ug, List<Unit> path) {
        Unit last=path.get(path.size()-1);//获得要扩展的path的最后一个节点，就是扩展开始点
        List<Unit> succs=ug.getSuccsOf(last);
        if(succs==null || succs.isEmpty()){
            return null;
        }
        boolean isFound=false;
        List<List<Unit>> allSuccsPath=new ArrayList<>();//存放所有的后继路径
        List<Unit> oneSuccsPath=new ArrayList<>();//某一条后继路径
        oneSuccsPath.add(last);
        allSuccsPath.add(oneSuccsPath);//先把第一个节点加上
        List<Unit> succsReturn=new ArrayList<>();//返回值
        while(!isFound){
            allSuccsPath.remove(oneSuccsPath);//拿出第一条路径
            List<Unit> temp=new ArrayList<>(oneSuccsPath);
            Unit current=oneSuccsPath.get(oneSuccsPath.size()-1);//当前路径的最后一个节点
            succs=ug.getSuccsOf(current);//该节点的所有后继
            //遍历每一个后继加到这条路径上，然后将这个路径add到所有后继路径中
            for(Unit succ:succs){
                List<Unit> temp2=new ArrayList<>(temp);
                temp2.add(succ);
                if(ug.getTails().contains(succ)){//如果发现遍历到尾节点就停止
                    isFound=true;
                    succsReturn=temp2;
                    return succsReturn;
                }
                else{//否则将该路径加到所有后继路径中
                    allSuccsPath.add(temp2);
                }
            }
            oneSuccsPath=allSuccsPath.get(0);//每次要处理的路径从第一个取，在最开始把第一个路径remove掉，加的时候都加在最后，保证每个路径都有机会被遍历。
        }
        return succsReturn;
    }

    /**
     * 拓展前继节点到heads入口
     * @param ug
     * @param path
     */
    private static List<Unit> extendPreds(UnitGraph ug, List<Unit> path) {
        Unit start=path.get(0);
        List<Unit> preds=ug.getPredsOf(start);
        if(preds==null|| preds.isEmpty()){
            return null;
        }
        boolean isFound=false;
        List<List<Unit>> allPredsPath=new ArrayList<>();
        List<Unit> onePredsPath=new ArrayList<>();
        onePredsPath.add(start);
        allPredsPath.add(onePredsPath);
        List<Unit> predsReturn=new ArrayList<>();
        while(!isFound){
            allPredsPath.remove(onePredsPath);
            List<Unit> temp=new ArrayList<>(onePredsPath);//?
            Unit current=onePredsPath.get(onePredsPath.size()-1);//因为add是加在后面所以是反过来的
            preds=ug.getPredsOf(current);
            for(Unit pred:preds){
                List<Unit> temp2=new ArrayList<>(temp);
                temp2.add(pred);

                if(ug.getHeads().contains(pred)){
                    isFound=true;
                    predsReturn=temp2;
                    return predsReturn;
                }
                else {
                    allPredsPath.add(temp2);
                }
            }
            onePredsPath=allPredsPath.get(0);
        }
        return predsReturn;
    }
    


    /**
     * 获得每个块的id
     * 它将哈希码中的每个数字字符映射为一个小写字母，生成一个新的字符串。
     * @param a
     * @return
     */
    private static String getID(Object a) {
//      String id = Integer.toString(System.identityHashCode(a));
        String id = Integer.toString(a.hashCode());
        StringBuilder newId = new StringBuilder();
        for (int i = 0; i < id.length(); i++) {
            newId.append((char) (97 + id.codePointAt(i) - "0".codePointAt(0)));//计算将数字字符映射为小写字母的偏移量，97 对应 'a' 的 Unicode 编码。
        }
        return newId.toString();
    }
}
